/**
 * Copyright(c) 2013 Jade Techonologies Co., Ltd.
 *
 * History:
 *   14-7-29 下午5:58 Created by dwzhang
 */
package com.jade.framework.base.comm.codec;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import com.jade.framework.base.comm.MessageCodec;
import com.jade.framework.base.comm.ProtocolSession;
import com.jade.framework.base.lang.Hex;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;

/**
 * 报文长度包含在前面几个字节的编解码器。
 *
 * @author dwzhang
 * @version 1.0 14-7-29 下午5:58
 */
public abstract class HeaderLengthCodec implements MessageCodec<ByteBuf, ByteBuf>
{
    protected int lenByteCounts = 4;
    protected boolean lenContainSelf = true;

    public HeaderLengthCodec()
    {
    }

    public HeaderLengthCodec(int lenByteCounts, boolean lenContainSelf)
    {
        if (lenByteCounts != 1 && lenByteCounts != 2 && lenByteCounts != 4) {
            throw new IllegalArgumentException("Illegal lenByteCounts " + lenByteCounts);
        }
        this.lenByteCounts = lenByteCounts;
        this.lenContainSelf = lenContainSelf;
    }


    public void encode(ProtocolSession session, ByteBuf byteBuf, Object message)
            throws IOException
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        doEncode(session, baos, message);
        int size = baos.size();
        int length = lenContainSelf ? size + lenByteCounts : size;
        writeLength(byteBuf, length);
        byteBuf.writeBytes(baos.toByteArray());
        if (session.getLog().isTraceEnabled()) {
            session.getLog().trace("=================MSG ENCODE==================");
            int i = byteBuf.readerIndex();
            byte[] msg = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(msg);
            byteBuf.readerIndex(i);
            String str = Hex.format(msg, 10, ' ');
            session.getLog().trace(str);
            session.getLog().trace("\n============================================");
        }
    }

    protected void writeLength(ByteBuf buf, int length)
    {
        if (lenByteCounts == 1) {
            buf.writeByte(length);
        }
        else if (lenByteCounts == 2) {
            buf.writeShort(length);
        }
        else {
            buf.writeInt(length);
        }
    }

    public abstract void doEncode(ProtocolSession session, OutputStream os, Object message)
            throws IOException;

    public Object decode(ProtocolSession session, ByteBuf byteBuf)
            throws IOException
    {
        if (byteBuf.readableBytes() < lenByteCounts) {
            session.getLog().trace("waiting for more bytes," + byteBuf.readByte() + "/" + lenByteCounts);
            return DecodeResult.NEED_MORE_INPUT;
        }
        int length = getLength(byteBuf);
        if (session.getLog().isTraceEnabled()) {
            session.getLog().trace("msg length:" + length);
        }
        int contentLength = lenContainSelf ? length - lenByteCounts : length;
        if (byteBuf.readableBytes() < contentLength) {
            return DecodeResult.NEED_MORE_INPUT;
        }
        if (session.getProtocol().getLog().isTraceEnabled()) {
            int i = byteBuf.readerIndex();
            byte[] msg = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(msg);
            String str = Hex.format(msg, 10, ' ');
            byteBuf.readerIndex(i);
            session.getLog().trace(str);
            session.getLog().trace("\n===================================");
        }
        InputStream in = new ByteBufInputStream(byteBuf, contentLength);

        return doDecode(session, length, in);
    }

    protected int getLength(ByteBuf buf)
    {
        if (lenByteCounts == 1) {
            return buf.readUnsignedByte();
        }
        else if (lenByteCounts == 2) {
            return buf.readUnsignedShort();
        }
        else {
            return buf.readInt();
        }
    }

    public abstract Object doDecode(ProtocolSession session, int length, InputStream in)
            throws IOException;
}
